#include <iostream>
#include <sstream>
#include <string>
#include <cassert>
#include <cstdlib>
#include <algorithm>

#include "listaComprimida.h"
using namespace std;



bool isSpace(char c) {
	return c == ' ' or c == '\t' or c == '\n';
}

template<typename T>
void asegurar(const T& t, string s, bool ignorarBlancos = true) {
	ostringstream out;
	out << t;
	string st = out.str();
	if(ignorarBlancos) remove_if(st.begin(), st.end(), isSpace);
	if (st != s) {
		cerr << s << " != " << out.str() << endl;
		assert(false);
	}
}

template<typename T, typename R>
void asegurar(const T& t, const R& r, bool ignorarBlancos = true) {
	ostringstream out;
	out << r;
	asegurar(t, out.str(), ignorarBlancos);
}

#define RUNTEST(t) { cerr << "Corriendo test " << #t << endl; t (); cerr << "Terminado test " << #t << endl; }

void testVacia() {
	ListaComprimida<int> l;
	ListaComprimida< ListaComprimida<int> > ll;
	ListaComprimida<int> l2(l);
	asegurar(l.esVacia(),true);
	asegurar(ll.esVacia(),true);
	asegurar(l2.esVacia(),true);
	asegurar(l, "[]");
	asegurar(ll, "[]");
	asegurar(l2, "[]");
}

void testCopia() {
	ListaComprimida<int> l(100,0);
	ListaComprimida< ListaComprimida<int> > ll;
	ll.agregarAtras(l,1);
	l.fin();
	ll.agregarAtras(l,2);
	ListaComprimida<int> l2(l);
	ListaComprimida< ListaComprimida<int> > l2l(ll);
	asegurar(l2, l);
	asegurar(l2l, ll);
}

void testComparar() {
	ListaComprimida<int> l(100,0);
	ListaComprimida<int> l2(l);

	ListaComprimida< ListaComprimida<int> > ll;
	ll.agregarAtras(l,1);

	if(!(l == l))
        assert(false);

	if(!(l == l))
        assert(false);

	if(!(l2 == l2))
        assert(false);

	if(!(l2 == l))
        assert(false);

    ListaComprimida<int> l3(100,0);

    if(!(l3 == l))
        assert(false);

    l3.fin();

    if(l3 == l)
        assert(false);

}

void testFinComienzo(){
	ListaComprimida<int> l;
	l.agregarAdelante(4,4);
	l.agregarAdelante(6,2);
	l.agregarAdelante(3);
	l.agregarAtras(2,1);
	l.agregarAtras(2,3);
	l.agregarAdelante(3);
	asegurar(l, "[3,3,6,6,4,4,4,4,2,2,2,2]");
	l.fin();
	asegurar(l, "[3,6,6,4,4,4,4,2,2,2,2]");
	l.fin();
	asegurar(l, "[6,6,4,4,4,4,2,2,2,2]");
	l.comienzo();
	asegurar(l, "[6,6,4,4,4,4,2,2,2]");
	l.comienzo();
	l.comienzo();
	l.comienzo();
	asegurar(l, "[6,6,4,4,4,4]");
	l.fin();
	l.comienzo();
	asegurar(l, "[6,4,4,4]");
	l.fin();
	l.comienzo();
	asegurar(l, "[4,4]");
	l.fin();
	l.comienzo();
	asegurar(l, "[]");
}

void testAgregar() {
	ListaComprimida<int> l;
	l.agregarAdelante(4,4);
	asegurar(l, "[4,4,4,4]");
	l.agregarAdelante(6,2);
	asegurar(l, "[6,6,4,4,4,4]");
	l.agregarAdelante(3);
	asegurar(l, "[3,6,6,4,4,4,4]");
	l.agregarAtras(2,1);
	asegurar(l, "[3,6,6,4,4,4,4,2]");
	l.agregarAtras(2,3);
	asegurar(l, "[3,6,6,4,4,4,4,2,2,2,2]");
	l.agregarAdelante(3);
	asegurar(l, "[3,3,6,6,4,4,4,4,2,2,2,2]");
	ListaComprimida< ListaComprimida<int> > ll;
	ll.agregarAdelante(l,2);
	l.fin();
	ll.agregarAtras(l,2);
	asegurar(ll, "[[3,3,6,6,4,4,4,4,2,2,2,2],[3,3,6,6,4,4,4,4,2,2,2,2],[3,6,6,4,4,4,4,2,2,2,2],[3,6,6,4,4,4,4,2,2,2,2]]");
	ll.fin();
	asegurar(ll, "[[3,3,6,6,4,4,4,4,2,2,2,2],[3,6,6,4,4,4,4,2,2,2,2],[3,6,6,4,4,4,4,2,2,2,2]]");
}

void testQuitarTodos() {
	ListaComprimida<int> l;
	l.agregarAdelante(4,2);
	l.agregarAdelante(6,2);
	l.agregarAdelante(4,2);
	l.agregarAdelante(5,2);
	l.agregarAdelante(4,2);
	l.agregarAdelante(6,2);
	l.agregarAdelante(4,2);

	l.quitarTodos(4);
	asegurar(l, "[6,6,5,5,6,6]");
	l.quitarTodos(5);
	asegurar(l, "[6,6,6,6]");
	l.quitarTodos(6);
	asegurar(l.esVacia(), true);
}

void testTakeDrop() {
    ListaComprimida<int> l;
	l.agregarAdelante(4,2);
	l.agregarAdelante(6,2);
	l.agregarAdelante(4,2);
	l.agregarAdelante(5,2);
	l.agregarAdelante(4,2);
	l.agregarAdelante(6,2);
	l.agregarAdelante(4,2);

    ListaComprimida<int> l2(l);
    l2.drop(0);
	asegurar(l2, l2);
	l2.drop(1);
	l2.drop(2);

	l.drop(3);

    asegurar(l, "[6,4,4,5,5,4,4,6,6,4,4]");

    asegurar(l, l2);

    l.take(11);
    l.take(15);
    asegurar(l, "[6,4,4,5,5,4,4,6,6,4,4]");

    l.take(10);
    asegurar(l, "[6,4,4,5,5,4,4,6,6,4]");

    l.take(4);
    asegurar(l, "[6,4,4,5]");

    l.take(1);
    asegurar(l, "[6]");

    l.take(0);
    asegurar(l, "[]");
}

#ifdef DEBUG
void testEstructura(){
    cout << "Corrinedo test testEstructura" << endl;

    ListaComprimida<int> l;
	l.agregarAdelante(4,2);
	l.agregarAdelante(6,2);
	l.agregarAdelante(4,2);
	l.agregarAdelante(5,2);
	l.agregarAdelante(4,2);
	l.agregarAdelante(6,2);
	l.agregarAdelante(4,2);

    ostringstream out;
	l.mostrarEstructura(out);
	ListaComprimida<int> l2(l);
	ostringstream out2;
	l2.mostrarEstructura(out2);

	asegurar(out2.str(), out.str());

	cout << "Terminado test testEstructura" << endl;
}

void testLong(){
    cout << "Corrinedo test testLong" << endl;
    ListaComprimida<int> l;
	l.agregarAdelante(4,2);
    l.agregarAtras(3,1);

    asegurar(l.longuitud(), 3);

    ListaComprimida<int> l2(l);

    asegurar(l2.longuitud(), 3);

    l2.agregarAdelante(8,3);
    l2.agregarAtras(7,3);

    asegurar(l2.longuitud(), 9);

    l2.quitarTodos(8);
    asegurar(l2.longuitud(), 6);

    l2.quitarTodos(4);

    asegurar(l2.longuitud(), 4);

    l2.quitarTodos(3);

    asegurar(l2.longuitud(), 3);

    l2.quitarTodos(7);

    asegurar(l2.longuitud(), 0);

    l2.agregarAdelante(9,6);
    l2.agregarAtras(1,2);
    l2.agregarAtras(8,3);
    l2.agregarAdelante(50,2);

    asegurar(l2.longuitud(), 13);

    l2.take(10);

    asegurar(l2.longuitud(), 10);

    l2.drop(2);

    asegurar(l2.longuitud(), 8);

    l2.take(2);

    asegurar(l2.longuitud(), 2);

    l2.take(0);

    asegurar(l2.longuitud(), 0);

    ListaComprimida<int> l3(l);

    l3.drop(20);

    asegurar(l3.longuitud(), 0);

    cout << "Terminado test testLong" << endl;
}
#endif

int main() {
	RUNTEST(testVacia);
	RUNTEST(testCopia);
	RUNTEST(testComparar);
	RUNTEST(testAgregar);
	RUNTEST(testFinComienzo);
	RUNTEST(testTakeDrop);
    RUNTEST(testQuitarTodos);

    #ifdef DEBUG
    testEstructura();
    testLong();
    #endif

	return 0;
}

